package top.cptr.simpleRpc.rpccore;

import cn.hutool.http.ContentType;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import jdk.internal.org.objectweb.asm.ClassReader;
import top.cptr.simpleRpc.rpccore.annotation.Rpc;
import top.cptr.simpleRpc.rpccore.command.rpc.ResultOk;
import top.cptr.simpleRpc.rpccore.annotation.RpcConfig;
import top.cptr.simpleRpc.rpccore.command.rpc.RequestRpc;
import top.cptr.simpleRpc.rpccore.exception.ExceptionFilter;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.JarURLConnection;
import java.net.URL;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;

/**
 * @author Tiger_L
 * @version 1.0
 * @description rpc入口程序
 * @date 2024/1/4 14:37
 */
public class RpcApplication {

    public static void run(Class<?> source) {
        RpcConfig rpcConfig = source.getAnnotation(RpcConfig.class);
        String scanPath = rpcConfig.scanPath();
        List<Class<?>> scanRpcClass = scanRpcClass(scanPath);
        RpcContContext rpcContContext = createRpcContContext(scanRpcClass);
        runServer(rpcConfig, rpcContContext);
    }

    private static List<Class<?>> scanRpcClass(String classPath) {
        ArrayList<Class<?>> classArrayList = new ArrayList<>();
        classPath = classPath.replace(".", "/");
        URL resource = RpcApplication.class.getClassLoader().getResource(classPath);
        assert resource != null;
//        file
        if ("file".equals(resource.getProtocol())) {
            File file = new File(resource.getFile());
            loadClass(file, classArrayList);
        } else {
//            jar
            try {
                JarURLConnection urlConnection = (JarURLConnection) resource.openConnection();
                Enumeration<JarEntry> entries = urlConnection.getJarFile().entries();
                loadJarClass(entries, classArrayList, classPath);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
        return classArrayList;

    }

    /**
     * 获取路径下所有的类
     *
     * @param file           路径
     * @param classArrayList class列表
     */
    private static void loadClass(File file, ArrayList<Class<?>> classArrayList) {
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            assert files != null;
            for (File f : files) {
                if (f.isDirectory()) {
                    loadClass(f, classArrayList);
                } else {
                    if (f.getName().endsWith(".class")) {
                        try {
                            ClassReader classReader = new ClassReader(Files.newInputStream(f.toPath()));
                            Class<?> aClass = Class.forName(classReader.getClassName().replace('/', '.'));
                            classArrayList.add(aClass);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }

    /**
     * 获取路径下所有的类
     *
     * @param entries        路径
     * @param classArrayList class列表
     */
    private static void loadJarClass(Enumeration<JarEntry> entries, ArrayList<Class<?>> classArrayList, String classPath) {
        classPath = classPath + "/";
        while (entries.hasMoreElements()) {
            JarEntry jarEntry = entries.nextElement();
            String entryName = jarEntry.getName();
            if (!jarEntry.isDirectory() && entryName.endsWith(".class")) {
                if (!entryName.startsWith(classPath)) {
                    continue;
                }
                try {
                    Class<?> aClass = Class.forName(entryName.replace('/', '.').replace(".class", ""));
                    classArrayList.add(aClass);
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    /**
     * 创建rpcContContext
     *
     * @param classArrayList 类列表
     * @return rpcContContext
     */
    private static RpcContContext createRpcContContext(List<Class<?>> classArrayList) {
        RpcContContext rpcContContext = new RpcContContext();
        classArrayList.forEach(item -> {
            try {
                Object o = item.newInstance();
                for (Method method : item.getMethods()) {
                    Rpc annotation = method.getAnnotation(Rpc.class);
                    if (annotation == null) {
                        continue;
                    }
                    String anMethodName = annotation.methodName();
                    String methodName = method.getName();
                    if (anMethodName != null && !"".equals(anMethodName)) {
                        methodName = anMethodName;
                    }
                    RpcItem rpcItem = RpcItem.build(o, method);
                    String log = String.format("---> class:%s#%s", item, methodName);
                    System.out.println(log);
                    rpcContContext.add(methodName, rpcItem);
                }
            } catch (ReflectiveOperationException e) {
                throw new RuntimeException(e.getMessage());
            }


        });
        return rpcContContext;
    }

    /**
     * 服务运行
     *
     * @param rpcConfig      rpc配置
     * @param rpcContContext rpc容器
     */
    private static void runServer(RpcConfig rpcConfig, RpcContContext rpcContContext) {
        HttpUtil.createServer(rpcConfig.port())
                .addFilter(new ExceptionFilter())
                .addAction(rpcConfig.serverPath(), (request, response) -> {
                    response.setContentType(ContentType.JSON.getValue());
                    if (!ContentType.JSON.getValue().equals(request.getContentType())) {
                        throw new RuntimeException("contentType must be application/json");
                    }
                    RequestRpc requestRpc;
                    try {
                        requestRpc = JSON.parseObject(request.getBody(), RequestRpc.class);
                    } catch (Exception e) {
                        throw new RuntimeException(e.getMessage());
                    }
                    String method = requestRpc.getMethod();
                    RpcItem rpcItem = rpcContContext.getRpcItem(method);
                    Object result = rpcItem.handle(requestRpc.getParams());
                    ResultOk resultOk = new ResultOk("2.0", result, requestRpc.getId());
                    response.write(JSON.toJSONString(resultOk));
                })
                .start();
    }

}
